package cn.lingyangwl.agile.system.service.impl;

import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.system.assembly.*;
import cn.lingyangwl.agile.system.manager.*;
import cn.lingyangwl.agile.system.mapper.*;
import cn.lingyangwl.agile.system.model.entity.*;
import cn.lingyangwl.agile.system.model.rqrs.article.*;
import cn.lingyangwl.agile.system.service.*;
import cn.lingyangwl.agile.tenant.core.utils.*;
import cn.lingyangwl.framework.lock.manager.*;
import cn.lingyangwl.framework.lock.model.*;
import cn.lingyangwl.framework.mybatis.plus.utils.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 文章
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class ArticleServiceImpl extends ServiceImpl<ArticleMapper, Article>
    implements IService<Article>, ArticleService {

    @Resource
    private ArticleManager articleManager;
    @Resource
    private ArticleGroupManager articleGroupManager;
    @Resource
    private ArticleAssembly articleAssembly;
    @Resource
    private ILockManager lockManager;
    
    @Override
    public IPage<ArticleResp> pageArticle(ArticlePageReq req) {
        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<Article> lqw = Article.lqw()
                .eq(Objects.nonNull(req.getGroupId()), Article::getGroupId, req.getGroupId())
                .ge(Objects.nonNull(beginTime), Article::getCreatedAt, beginTime)
                .like(StringUtils.isNotEmpty(req.getTitle()), Article::getTitle, req.getTitle())
                .le(Objects.nonNull(endTime), Article::getCreatedAt, endTime)
                .orderByAsc(Article::getSortNo)
            .orderByDesc(Article::getCreatedAt);
        IPage<Article> pageData = this.page(PageUtil.to(req), lqw);

        Set<Long> groupIds = pageData.getRecords().stream().map(Article::getGroupId).filter(Objects::nonNull)
                .collect(Collectors.toSet());
        // 避免为空查询报错
        groupIds.add(-1L);

        Map<Long, String> groupMap = articleGroupManager.listByIds(groupIds).stream()
                .collect(Collectors.toMap(ArticleGroup::getId, ArticleGroup::getName));

        // 将虚拟访问量和真实访问量相加
        pageData.getRecords().forEach(e -> e.setVisit(e.getVisit() + e.getVisitVirtual()));
        return PageUtil.to(pageData, (e) -> {
            ArticleResp resp = articleAssembly.toArticleResp(e);
            resp.setGroupName(groupMap.getOrDefault(e.getGroupId(), ""));
            return resp;
        });
    }

    @Override
    public ArticleResp getArticle(ArticleGetReq req) {
        DataIsolateLevel level = req.getIsFormSceneTenant() ? DataIsolateLevel.ALONE : DataIsolateLevel.MGT;
        return TenantUtils.execute(level, () -> {
            LambdaQueryWrapper<Article> lqw = Article.lqw()
                .eq(StringUtils.isNotEmpty(req.getCode()), Article::getCode, req.getCode())
                .eq(Objects.nonNull(req.getId()), Article::getId, req.getId());
            Article entity = this.getOne(lqw);
            if (Objects.isNull(entity)) {
                return null;
            }
            // 将虚拟访问量和真实访问量相加
            entity.setVisit(entity.getVisit() + entity.getVisitVirtual());
            return articleAssembly.toArticleResp(entity);
        });
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean saveArticle(ArticleSaveReq req) {
        Article entity = articleAssembly.toArticle(req);
        if (StringUtils.isNotEmpty(req.getCode())) {
            ILock lock = lockManager.getLock("lock:article:" + req.getCode());
            try {
                lock.lock();
                LambdaQueryWrapper<Article> lqw = Article.lqw()
                    .eq(StringUtils.isNotEmpty(req.getCode()), Article::getCode, req.getCode());
                if (this.count(lqw) > 0) {
                    throw new BizException("文章标识 [{}] 已存在", req.getCode());
                }
                articleGroupManager.addArticleNum(entity.getGroupId());
                return this.save(entity);
            } finally {
                lock.unlock();
            }
        }
        articleGroupManager.addArticleNum(entity.getGroupId());
        return this.save(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateArticle(ArticleUpdateReq req) {
        Article newEntity = articleAssembly.toArticle(req);
        Article oldEntity = this.getById(newEntity.getId());
        if (Objects.isNull(oldEntity)) {
            return false;
        }
        if (StringUtils.isNotEmpty(req.getCode())) {
            ILock lock = lockManager.getLock("lock:article:" + req.getCode());
            try {
                lock.lock();
                LambdaQueryWrapper<Article> lqw = Article.lqw()
                    .eq(StringUtils.isNotEmpty(req.getCode()), Article::getCode, req.getCode())
                    .ne(Article::getId, req.getId());
                if (this.count(lqw) > 0) {
                    throw new BizException("文章标识 [{}] 已存在", req.getCode());
                }
                articleGroupManager.decreaseArticleNum(oldEntity.getGroupId());
                articleGroupManager.addArticleNum(newEntity.getGroupId());
                return this.updateById(newEntity);
            } finally {
                lock.unlock();
            }
        }
        // 将原有的关联文章组的文章数量减1, 新关联的文章组的文章数加1
        articleGroupManager.decreaseArticleNum(oldEntity.getGroupId());
        articleGroupManager.addArticleNum(newEntity.getGroupId());
        return this.updateById(newEntity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteArticle(Long id) {
        Article article = this.getById(id);
        if (Objects.isNull(article)) {
            return false;
        }
        articleGroupManager.decreaseArticleNum(article.getGroupId());
    	return this.removeById(id);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean batchDeleteArticle(List<Long> ids) {
        List<Article> articles = this.listByIds(ids);
        List<Long> groupIds = articles.stream().map(Article::getGroupId).filter(Objects::nonNull)
                .collect(Collectors.toList());
        articleGroupManager.batchDecreaseArticleNum(groupIds);
    	return this.removeByIds(ids);
    }
}
